Micron Document
πŸŽ–οΈGitΠ―Ρ€Π°πŸŽ–οΈ

Node / meshtastic / Meshtastic-Android / files / docs / BUILD_LOGIC_CONVENTIONS_GUIDE.md

Displaying Rendered β€’ View raw β€’ Download

docs/BUILD_LOGIC_CONVENTIONS_GUIDE.md f07624be882e679affc02931dc75e8cb1594de18 (f07624be) Text, 10.29 KB

Build-Logic Convention Patterns & Guidelines

Quick reference for maintaining and extending the build-logic convention system.

Core Principles

1. DRY (Don't Repeat Yourself): Extract common configuration into functions
2. Clarity Over Cleverness: Explicit intent in T383838build.gradle.kts files matters
3. Single Responsibility: Each convention plugin has one clear purpose
4. Test-Driven: Configuration changes must pass T383838spotlessCheck, T383838detekt, and tests

Convention Plugin Architecture

T282828
build-logic/
β”œβ”€β”€ convention/
β”‚ β”œβ”€β”€ src/main/kotlin/
β”‚ β”‚ β”œβ”€β”€ KmpFeatureConventionPlugin.kt # KMP feature modules (composes library + compose + koin + common deps)
β”‚ β”‚ β”œβ”€β”€ KmpLibraryConventionPlugin.kt # KMP modules: core libraries
β”‚ β”‚ β”œβ”€β”€ KmpLibraryComposeConventionPlugin.kt # KMP Compose Multiplatform setup
β”‚ β”‚ β”œβ”€β”€ KmpJvmAndroidConventionPlugin.kt # Opt-in jvmAndroidMain hierarchy for Android + desktop JVM
β”‚ β”‚ β”œβ”€β”€ AndroidApplicationConventionPlugin.kt # Main app
β”‚ β”‚ β”œβ”€β”€ AndroidLibraryConventionPlugin.kt # Android-only libraries
β”‚ β”‚ β”œβ”€β”€ AndroidApplicationComposeConventionPlugin.kt
β”‚ β”‚ β”œβ”€β”€ AndroidLibraryComposeConventionPlugin.kt
β”‚ β”‚ β”œβ”€β”€ org/meshtastic/buildlogic/
β”‚ β”‚ β”‚ β”œβ”€β”€ KotlinAndroid.kt # Base Kotlin/Android config
β”‚ β”‚ β”‚ β”œβ”€β”€ AndroidCompose.kt # Compose setup
β”‚ β”‚ β”‚ β”œβ”€β”€ FlavorResolution.kt # Flavor configuration
β”‚ β”‚ β”‚ β”œβ”€β”€ MeshtasticFlavor.kt # Flavor definitions
β”‚ β”‚ β”‚ β”œβ”€β”€ Detekt.kt # Static analysis
β”‚ β”‚ β”‚ β”œβ”€β”€ Spotless.kt # Code formatting
β”‚ β”‚ β”‚ └── ... (other config modules)

How to Add a New Convention

Example: Adding a new test framework dependency

Current Pattern (GOOD βœ…):

If all KMP modules need a dependency, add it to T383838KotlinAndroid.kt::configureKmpTestDependencies():

T282828
Tff7b72internal Tff7b72fun Te6edf3ProjectTb4b4b4.Td2a8ffconfigureKmpTestDependenciesTb4b4b4(Tb4b4b4) Tb4b4b4{
Te6edf3extensionsTb4b4b4.Te6edf3configureTff7b72<Te6edf3KotlinMultiplatformExtensionTff7b72> Tb4b4b4{
Te6edf3sourceSetsTb4b4b4.Te6edf3apply Tb4b4b4{
Tff7b72val Te6edf3commonTest Tff7b72= Te6edf3findByNameTb4b4b4(Ta5d6ff"Ta5d6ffcommonTestTa5d6ff"Tb4b4b4) Tff7b72?: Tff7b72returnTf0883e@apply
Te6edf3commonTestTb4b4b4.Te6edf3dependencies Tb4b4b4{
Te6edf3implementationTb4b4b4(Te6edf3kotlinTb4b4b4(Ta5d6ff"Ta5d6fftestTa5d6ff"Tb4b4b4)Tb4b4b4)
T8b949e// NEW: Add here once, applies to all ~15 KMP modules
Te6edf3implementationTb4b4b4(Te6edf3libsTb4b4b4.Te6edf3libraryTb4b4b4(Ta5d6ff"Ta5d6ffnew-test-frameworkTa5d6ff"Tb4b4b4)Tb4b4b4)
Tb4b4b4}
T8b949e// ... androidHostTest setup
Tb4b4b4}
Tb4b4b4}
Tb4b4b4}


Result: All 15 feature and core modules automatically get the dependency βœ…

Example: Adding shared T383838jvmAndroidMain code to a KMP module

Current Pattern (GOOD βœ…):

If a KMP module needs Java/JVM APIs shared between Android and desktop JVM, apply the opt-in convention plugin instead of manually creating source sets and T383838dependsOn(...) edges:

T282828
Te6edf3plugins Tb4b4b4{
Te6edf3aliasTb4b4b4(Te6edf3libsTb4b4b4.Te6edf3pluginsTb4b4b4.Te6edf3meshtasticTb4b4b4.Te6edf3kmpTb4b4b4.Te6edf3libraryTb4b4b4)
Te6edf3idTb4b4b4(Ta5d6ff"Ta5d6ffmeshtastic.kmp.jvm.androidTa5d6ff"Tb4b4b4)
Tb4b4b4}

Te6edf3kotlin Tb4b4b4{
Te6edf3jvmTb4b4b4(Tb4b4b4)
Te6edf3android Tb4b4b4{ T8b949e/* ... */ Tb4b4b4}

Te6edf3sourceSets Tb4b4b4{
Te6edf3commonMainTb4b4b4.Te6edf3dependencies Tb4b4b4{ T8b949e/* ... */ Tb4b4b4}
Te6edf3jvmMainTb4b4b4.Te6edf3dependencies Tb4b4b4{ T8b949e/* jvm-only additions */ Tb4b4b4}
Te6edf3androidMainTb4b4b4.Te6edf3dependencies Tb4b4b4{ T8b949e/* android-only additions */ Tb4b4b4}
Tb4b4b4}
Tb4b4b4}


Why: The convention uses Kotlin's hierarchy template API to create T383838jvmAndroidMain without the T383838Default Kotlin Hierarchy Template Not Applied Correctly warning triggered by hand-written T383838dependsOn(...) graphs.

Example: Creating a new KMP feature module

Current Pattern (GOOD βœ…):

Use T383838meshtastic.kmp.feature for any T383838feature:* module. It composes T383838kmp.library + T383838kmp.library.compose + T383838koin and provides all the common Compose/Lifecycle/Koin/Android dependencies that every feature needs:

T282828
Te6edf3plugins Tb4b4b4{
Te6edf3aliasTb4b4b4(Te6edf3libsTb4b4b4.Te6edf3pluginsTb4b4b4.Te6edf3meshtasticTb4b4b4.Te6edf3kmpTb4b4b4.Te6edf3featureTb4b4b4)
T8b949e// Optional: add only if this feature needs serialization
Te6edf3aliasTb4b4b4(Te6edf3libsTb4b4b4.Te6edf3pluginsTb4b4b4.Te6edf3meshtasticTb4b4b4.Te6edf3kotlinxTb4b4b4.Te6edf3serializationTb4b4b4)
Tb4b4b4}

Te6edf3kotlin Tb4b4b4{
Te6edf3jvmTb4b4b4(Tb4b4b4)
Te6edf3android Tb4b4b4{
Te6edf3namespace Tff7b72= Ta5d6ff"Ta5d6fforg.meshtastic.feature.yourfeatureTa5d6ff"
Te6edf3androidResourcesTb4b4b4.Te6edf3enable Tff7b72= Tff7b72false
Te6edf3withHostTest Tb4b4b4{ Te6edf3isIncludeAndroidResources Tff7b72= Tff7b72true Tb4b4b4}
Tb4b4b4}

Te6edf3sourceSets Tb4b4b4{
Te6edf3commonMainTb4b4b4.Te6edf3dependencies Tb4b4b4{
T8b949e// Only module-SPECIFIC deps here
Te6edf3implementationTb4b4b4(Te6edf3projectsTb4b4b4.Te6edf3coreTb4b4b4.Te6edf3commonTb4b4b4)
Te6edf3implementationTb4b4b4(Te6edf3projectsTb4b4b4.Te6edf3coreTb4b4b4.Te6edf3modelTb4b4b4)
Te6edf3implementationTb4b4b4(Te6edf3projectsTb4b4b4.Te6edf3coreTb4b4b4.Te6edf3uiTb4b4b4)
Tb4b4b4}
Te6edf3androidMainTb4b4b4.Te6edf3dependencies Tb4b4b4{
T8b949e// Only Android-specific extras here
Tb4b4b4}
Tb4b4b4}
Tb4b4b4}


What the plugin provides automatically:
β€’ T383838commonMain: T383838compose-multiplatform-material3, T383838compose-multiplatform-materialIconsExtended, T383838jetbrains-lifecycle-viewmodel-compose, T383838koin-compose-viewmodel, T383838kermit
β€’ T383838androidMain: T383838androidx-compose-bom (platform), T383838accompanist-permissions, T383838androidx-activity-compose, T383838androidx-compose-material3, T383838androidx-compose-material-iconsExtended, T383838androidx-compose-ui-text, T383838androidx-compose-ui-tooling-preview
β€’ T383838commonTest: T383838core:testing

Why: Eliminates ~15 duplicate dependency declarations per feature module (modelled after Now in Android's T383838AndroidFeatureImplConventionPlugin).

Example: Adding Android-specific test config

Pattern: Add to T383838AndroidLibraryConventionPlugin.kt:

T282828
Te6edf3extensionsTb4b4b4.Te6edf3configureTff7b72<Te6edf3LibraryExtensionTff7b72> Tb4b4b4{
Te6edf3configureKotlinAndroidTb4b4b4(Tff7b72thisTb4b4b4)
Te6edf3testOptionsTb4b4b4.Te6edf3apply Tb4b4b4{
Te6edf3animationsDisabled Tff7b72= Tff7b72true
T8b949e// NEW: Android-specific test config
Te6edf3unitTestsTb4b4b4.Te6edf3isIncludeAndroidResources Tff7b72= Tff7b72true
Tb4b4b4}
Tb4b4b4}


Alternative: If it applies to both app and library, consider extracting a function:

T282828
Tff7b72internal Tff7b72fun Te6edf3ProjectTb4b4b4.Td2a8ffconfigureAndroidTestOptionsTb4b4b4(Tb4b4b4) Tb4b4b4{
Te6edf3extensionsTb4b4b4.Te6edf3configureTff7b72<Te6edf3CommonExtensionTff7b72> Tb4b4b4{
Te6edf3testOptionsTb4b4b4.Te6edf3apply Tb4b4b4{
Te6edf3animationsDisabled Tff7b72= Tff7b72true
T8b949e// Shared test options
Tb4b4b4}
Tb4b4b4}
Tb4b4b4}


Duplication Heuristics

When to consolidate (DRY):
β€’ βœ… Configuration appears in 3+ convention plugins
β€’ βœ… The duplication changes together (same reasons to update)
β€’ βœ… Extraction doesn't require complex type gymnastics
β€’ βœ… Underlying Gradle extension is the same (T383838CommonExtension)

When to keep separate (Clarity):
β€’ βœ… Different Gradle extension types (T383838ApplicationExtension vs T383838LibraryExtension)
β€’ βœ… Plugin intent is explicit in T383838build.gradle.kts usage
β€’ βœ… Duplication is small (<50 lines) and stable
β€’ βœ… Future divergence between app/library handling is plausible

Examples in codebase:

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”
β”‚ Duplication β”‚ Status β”‚ Re… β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€
β”‚ T383838AndroidApplicationComposeConventionPlugin β‰ˆ T383838AndroidLibraryComposeConventi… β”‚ Kept Separate β”‚ Di… β”‚
β”‚ T383838AndroidApplicationFlavorsConventionPlugin β‰ˆ T383838AndroidLibraryFlavorsConventi… β”‚ Kept Separate β”‚ Di… β”‚
β”‚ T383838configureKmpTestDependencies() (7 modules) β”‚ Consolidated β”‚ La… β”‚
β”‚ T383838jvmAndroidMain hierarchy setup (4 modules) β”‚ Consolidated β”‚ Sh… β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”˜

Testing Convention Changes

After modifying a convention plugin, verify:

T282828
T8b949e# 1. Code quality
./gradlew spotlessCheck detekt

T8b949e# 2. Compilation
./gradlew assembleDebug assembleRelease

T8b949e# 3. Tests
./gradlew Tffa657test T8b949e# All unit tests
./gradlew :feature:messaging:jvmTest T8b949e# Feature module tests
./gradlew :feature:node:testAndroidHostTest T8b949e# Android host tests


Documentation Requirements

When you add/modify a convention:

1. Add Kotlin docs to the function:
T282828
T8b949e/**
* Configure test dependencies for KMP modules.
*
* Automatically applies kotlin("test") to:
* - commonTest source set (all targets)
* - androidHostTest source set (Android-only)
*
* Usage: Called automatically by KmpLibraryConventionPlugin
*/
Tff7b72internal Tff7b72fun Te6edf3ProjectTb4b4b4.Td2a8ffconfigureKmpTestDependenciesTb4b4b4(Tb4b4b4) Tb4b4b4{ Tb4b4b4.Tb4b4b4.Tb4b4b4. Tb4b4b4}


2. Update AGENTS.md if convention affects developers
3. Update this guide if pattern changes

Performance Tips

β€’ Configuration-time: Convention logic runs during Gradle configuration (0.5-2s)
β€’ Build-time: No impact (conventions don't execute tasks)
β€’ Optimization focus: Minimize T383838extensions.configure() blocks (lazy evaluation is preferred)

Good βœ…
T282828
Te6edf3extensionsTb4b4b4.Te6edf3configureTff7b72<Te6edf3KotlinMultiplatformExtensionTff7b72> Tb4b4b4{
T8b949e// Single block for all source set configuration
Te6edf3sourceSetsTb4b4b4.Te6edf3apply Tb4b4b4{
Te6edf3commonTestTb4b4b4.Te6edf3dependencies Tb4b4b4{ T8b949e/* ... */ Tb4b4b4}
Te6edf3androidHostTestTff7b72?.Te6edf3dependencies Tb4b4b4{ T8b949e/* ... */ Tb4b4b4}
Tb4b4b4}
Tb4b4b4}


Avoid ❌
T282828
T8b949e// Multiple blocks - slower configuration
Te6edf3extensionsTb4b4b4.Te6edf3configureTff7b72<Te6edf3KotlinMultiplatformExtensionTff7b72> Tb4b4b4{
Te6edf3sourceSetsTb4b4b4.Te6edf3getByNameTb4b4b4(Ta5d6ff"Ta5d6ffcommonTestTa5d6ff"Tb4b4b4)Tb4b4b4.Te6edf3dependencies Tb4b4b4{ T8b949e/* ... */ Tb4b4b4}
Tb4b4b4}
Te6edf3extensionsTb4b4b4.Te6edf3configureTff7b72<Te6edf3KotlinMultiplatformExtensionTff7b72> Tb4b4b4{
Te6edf3sourceSetsTb4b4b4.Te6edf3getByNameTb4b4b4(Ta5d6ff"Ta5d6ffandroidHostTestTa5d6ff"Tb4b4b4)Tb4b4b4.Te6edf3dependencies Tb4b4b4{ T8b949e/* ... */ Tb4b4b4}
Tb4b4b4}


Common Pitfalls

❌ Mistake: Adding dependencies in the wrong place
T282828
T8b949e// WRONG: Adds to ALL modules, not just KMP
Te6edf3extensionsTb4b4b4.Te6edf3configureTff7b72<Te6edf3ProjectTff7b72> Tb4b4b4{
Te6edf3dependencies Tb4b4b4{ Te6edf3addTb4b4b4(Ta5d6ff"Ta5d6ffimplementationTa5d6ff"Tb4b4b4, Tb4b4b4.Tb4b4b4.Tb4b4b4.Tb4b4b4) Tb4b4b4} T8b949e// Global!
Tb4b4b4}

T8b949e// RIGHT: Scoped to specific source set/module type
Te6edf3commonTestTb4b4b4.Te6edf3dependencies Tb4b4b4{ Te6edf3implementationTb4b4b4(Tb4b4b4.Tb4b4b4.Tb4b4b4.Tb4b4b4) Tb4b4b4}


❌ Mistake: Extension type mismatch
T282828
T8b949e// WRONG: LibraryExtension isn't a subtype of ApplicationExtension
Te6edf3extensionsTb4b4b4.Te6edf3configureTff7b72<Te6edf3ApplicationExtensionTff7b72> Tb4b4b4{
T8b949e// Won't apply to library modules
Tb4b4b4}

T8b949e// RIGHT: Use CommonExtension or specific types
Te6edf3extensionsTb4b4b4.Te6edf3configureTff7b72<Te6edf3CommonExtensionTff7b72> Tb4b4b4{
T8b949e// Applies to both
Tb4b4b4}


❌ Mistake: Side effects during configuration
T282828
T8b949e// WRONG: Eager task configuration at plugin-apply time
Te6edf3tasksTb4b4b4.Te6edf3withTypeTff7b72<Te6edf3TestTff7b72> Tb4b4b4{
T8b949e// Can realize tasks too early
Tb4b4b4}

T8b949e// RIGHT: Lazy, configuration-cache-friendly wiring
Te6edf3tasksTb4b4b4.Te6edf3withTypeTff7b72<Te6edf3TestTff7b72>Tb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3configureEach Tb4b4b4{
T8b949e// Applies to existing and future tasks lazily
Tb4b4b4}


Related Files

β€’ T383838AGENTS.md - Development guidelines (Section 3.B testing, Section 4.A build protocol)
β€’ T383838build-logic/convention/build.gradle.kts - Convention plugin build config

Served by rngit 1.5.0 - Generated in 0.07s